#include "delivery_cleaner.h"
#include "log.h"
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>

#include <map>

namespace edm{
namespace prepare{

delivery_cleaner_t::delivery_cleaner_t():
m_started(false),m_callback_clean(0),m_callback_error(0),
m_io_work(new boost::asio::io_service::work(m_io_service)),m_thread(0)
{
    logtrace((PREPARE, "delivery_cleaner_t::delivery_cleaner_t"));
}

delivery_cleaner_t::~delivery_cleaner_t()
{
    logtrace((PREPARE, "delivery_cleaner_t::~delivery_cleaner_t begin ..."));
    if(m_started) stop();
    logtrace((PREPARE, "delivery_cleaner_t::~delivery_cleaner_t end ok."));
}
//! start cleaner
int delivery_cleaner_t::start(callback_clean_t callback_clean, callback_error_t callback_error)
{
    logtrace((PREPARE, "delivery_cleaner_t::start begin ..."));
    if(m_started)
    {
        logwarn((PREPARE, "delivery_cleaner_t::start already started."));
        return 0;
    }
    
    m_callback_clean = callback_clean;
    m_callback_error = callback_error;
    
    m_thread.reset(new boost::thread(
    boost::bind(&boost::asio::io_service::run, &m_io_service)));
       
    m_started = true;
    
    logtrace((PREPARE, "delivery_cleaner_t::start end ok."));
    return 0;
}
//! stop cleaner
int delivery_cleaner_t::stop()
{
    logtrace((PREPARE, "delivery_cleaner_t::stop begin ..."));
    if(!m_started)
    {
        logwarn((PREPARE, "delivery_cleaner_t::stop already stopped."));
        return 0;
    }
    //! if there is left not cleaned
    std::map<pid_t, delivery_t*>::iterator it;
    for(it = m_deliverys.begin(); it != m_deliverys.end(); it++)
    {
        //! kill 
        kill(it->first, SIGUSR1);
        //! wait for stop
        trigger_wait_hang();
    }

    //! clear all un catched delivery
    trigger_clear();
    //!
    m_io_work.reset();
    m_thread->join();
    m_started = false;
    
    logtrace((PREPARE, "delivery_cleaner_t::stop end ok."));
    return 0;
}

//! trigger clean
void delivery_cleaner_t::trigger_wait_nohang()
{
    logtrace((PREPARE, "delivery_cleaner_t::trigger_wait_nohang"));
    m_io_service.post(boost::bind(&delivery_cleaner_t::trigger_wait_i, this, false));
}
void delivery_cleaner_t::trigger_wait_hang()
{
    logtrace((PREPARE, "delivery_cleaner_t::trigger_wait_hang"));
    m_io_service.post(boost::bind(&delivery_cleaner_t::trigger_wait_i, this, true));
}

void delivery_cleaner_t::trigger_clear()
{
    logtrace((PREPARE, "delivery_cleaner_t::trigger_clear"));
    m_io_service.post(boost::bind(&delivery_cleaner_t::trigger_clear_i, this));
}

//! insert a new clean delivery
void delivery_cleaner_t::delivery_clean(pid_t pid, delivery_t* delivery)
{
    logtrace((PREPARE, "delivery_cleaner_t::delivery_clean"));
    m_io_service.post(boost::bind(&delivery_cleaner_t::delivery_clean_i, this, pid, delivery));
}

void delivery_cleaner_t::clear(pid_t pid, int status)
{
    logtrace((PREPARE, "delivery_cleaner_t::clear"));
    m_io_service.post(boost::bind(&delivery_cleaner_t::clear_i, this, pid, status));
}

void delivery_cleaner_t::clear_i(pid_t pid, int status)
{
    logtrace((PREPARE, "delivery_cleaner_t::clear_i pid:<%ld>", pid));
    if(m_deliverys.find(pid) == m_deliverys.end())
    {
        logwarn((PREPARE, "CLEAR: child <%ld> not exist in cleaner.", pid));
        return;
    }    

    m_deliverys[pid]->state = delivery_t::DELIVERIED;
    //! loginfo((PREPARE, "delivery_cleaner_t::trigger_wait_nohang_i wait pid:<%ld> status:<%d>.",
    //!        pid, WEXITSTATUS(status)));
    if(WEXITSTATUS(status) >= edm::common::SMTP_FAILED)
    {
        m_deliverys[pid]->smtp_high_code = edm::common::SMTP_FAILED;
        m_deliverys[pid]->smtp_low_code = (edm::common::SMTP_LOW_CODE)(WEXITSTATUS(status) - edm::common::SMTP_FAILED);
    }
    else if(WEXITSTATUS(status) >= edm::common::SMTP_RETRY)
    {
        m_deliverys[pid]->smtp_high_code = edm::common::SMTP_RETRY;
        m_deliverys[pid]->smtp_low_code = (edm::common::SMTP_LOW_CODE)(WEXITSTATUS(status) - edm::common::SMTP_RETRY);
    }
    else{
        m_deliverys[pid]->smtp_high_code = edm::common::SMTP_OK;
        m_deliverys[pid]->smtp_low_code = edm::common::MX_S_OK;
    }


    delivery_t* delivery = m_deliverys[pid];
    m_deliverys.erase(pid);
    if(m_callback_clean) m_callback_clean(delivery);
}

void delivery_cleaner_t::trigger_clear_i()
{
    //! if there is left not cleaned
    std::map<pid_t, delivery_t*>::iterator it;
    for(it = m_deliverys.begin(); it != m_deliverys.end(); it++)
    {
        it->second->state = delivery_t::DELIVERIED;
        it->second->smtp_low_code = edm::common::OPERATOR_DELETED;
        if(m_callback_error) m_callback_error(it->second);
    }
}

void delivery_cleaner_t::trigger_wait_i(bool hang)
{
    logtrace((PREPARE, "delivery_cleaner_t::trigger_wait_i hang:<%s> begin ...",
             (hang ? "true":"false")));
    pid_t pid;
    int status;
    if(hang)
    {
        pid = waitpid(-1, &status, 0);
        if(pid < 0)
        {
            logerror((PREPARE, "delivery_cleaner_t::trigger_wait_nohang_i wait failed:<%s>.",
                      strerror(errno)));
            return;
        }
        else if(pid == 0)
        {
            //logwarn((PREPARE, "delivery_cleaner_t::trigger_wait_nohang_i wait no child."));
            return;
        }
        else
        {
            clear_i(pid, status);
        }
    }
    else
    {
        while((pid = waitpid(-1, &status, WNOHANG)) > 0)
        {
            clear_i(pid, status);
        }
        
        if(pid < 0)
        {
            logerror((PREPARE, "delivery_cleaner_t::trigger_wait_nohang_i wait failed:<%s>.",
                      strerror(errno)));
            return;
        }
        else if(pid == 0)
        {
            //! logwarn((PREPARE, "delivery_cleaner_t::trigger_wait_nohang_i wait no child."));
            return;
        }
    }
        
    
    logtrace((PREPARE, "delivery_cleaner_t::trigger_wait_nohang_i end ok."));
}

void delivery_cleaner_t::delivery_clean_i(pid_t pid, delivery_t* delivery)
{
    logtrace((PREPARE, "delivery_cleaner_t::delivery_clean_i begin ..."));
    m_deliverys[pid] = delivery;
    logtrace((PREPARE, "delivery_cleaner_t::delivery_clean_i end ok."));  
}
    
}}
